﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.IO;
using System.Xml;
using System.Text.RegularExpressions;
using System.Net.Sockets;
using System.Xml.Linq;

namespace NiconicoAPI {
    /// <summary>
    /// 放送情報を保持するクラスです。
    /// </summary>
    public class Broadcast {
        private const string Url_GetPlayerStatus = "http://watch.live.nicovideo.jp/api/getplayerstatus?";
        private string _broadcastID;
        private string _commentIP;
        private string _commentPort;
        private string _threadID;
        private Account _user;
        public List<Comment> CommnetList { get; private set; }
    
        public event EventHandler<CommnetRecivedEventArgs> CommnetRecived;

        /// <summary>
        /// 指定した放送IDから放送情報を取得します。
        /// </summary>
        /// <param name="broadcastID">放送ID</param>
        /// <param name="user">ユーザ情報</param>
        public Broadcast(string broadcastID, Account user) {
            _broadcastID = broadcastID;
            _user = user;
            CommnetList = new List<Comment>();

            getCommentServerInfo();
        }
        /// <summary>
        /// コメントサーバ情報を取得する。
        /// </summary>
        private void getCommentServerInfo() {
            var param = "v=" + _broadcastID;
            var response = WebLogic.HttpGet(Url_GetPlayerStatus + param, _user.NiconicoCokie);
            using (StringReader sr = new StringReader(response)) {
                XDocument doc = XDocument.Load(sr);
                var selectStauts = from p in doc.Descendants("getplayerstatus")
                                   select new {
                                       Status = (string)p.Attribute("status"),
                                   };

                var selectCommentInfo = from p in doc.Descendants("ms")
                                        select new {
                                            Address = (string)p.Element("addr"),
                                            Port = (string)p.Element("port"),
                                            Thread = (string)p.Element("thread")
                                        };

                var result = selectCommentInfo.First();
                _commentIP = result.Address;
                _commentPort = result.Port;
                _threadID = result.Thread;
            }
        }

        /// <summary>
        /// コメントの取得を開始します。
        /// コメントが取得されるとCommnetRecivedイベントが発生します。
        /// </summary>
        /// <param name="res_form"></param>
        public void StartComment(int res_form) {
            //コメントサーバに接続
            //送信文字列を生成
            //TODO:TCPの理解が出来てない。ほんとにこれで良いのか？
            //TODO:TCPってことはロストしないでデータが飛んでくるんだよな
            string sendData = string.Format(@"<thread thread=""{0}"" version=""20061206"" res_from=""{1}"" />\0", _threadID, res_form);
            TcpClient tcp = new TcpClient(_commentIP, int.Parse(_commentPort));
            using (NetworkStream ns = tcp.GetStream()) {
                byte[] sendBytes = Encoding.UTF8.GetBytes(sendData);
                sendBytes[sendBytes.Length - 1] = 0;
                //Request送信
                ns.Write(sendBytes, 0, sendBytes.Length);

                int resSize;
                string lastChatData = null;    //最後に読み込んだ文字列
                bool? isValidChatData = null;
                while (ns.CanRead) {
                    var resBytes = new byte[500];
                    resSize = ns.Read(resBytes, 0, resBytes.Length);
                    if (resSize == 0) {
                        break;
                    }

                    var message = Encoding.UTF8.GetString(resBytes);
                    var elements = message.Split(new string[] { "\0" }, StringSplitOptions.RemoveEmptyEntries);

                    foreach (string elm in elements) {
                        string chatData = elm;
                        if (isValidChatData.HasValue && !isValidChatData.Value){
                            chatData = lastChatData + elm;       
                        }


                        lastChatData = chatData;
                        Comment com = createComment(elm);
                        
                        if (com != null) {
                            isValidChatData = true;
                        } else {
                            isValidChatData = false;
                            continue;
                        }

                        CommnetList.Add(com);
                        OnCommentRecived(new CommnetRecivedEventArgs(com));
                    }
                }
            }
        }
        /// <summary>
        /// サーバーから送られてきたXMLのコメント情報を解析し
        /// Commentオブジェクトを生成する。
        /// </summary>
        /// <param name="chatData"></param>
        /// <returns></returns>
        private Comment createComment(string chatData) {
            XDocument doc = null;
            try {
                doc = XDocument.Parse(chatData);
            } catch (XmlException) {
                return null;
            }

            var query = from c in doc.Descendants("chat")
                        select new {
                            Anonymity = (bool?)c.Attribute("anonymity"),
                            UnixTimeStamp = (long)c.Attribute("date"),
                            Number = (int)c.Attribute("no"),
                            UserLevel = (int?)c.Attribute("premium"),
                            UserID = (string)c.Attribute("user_id"),
                            Text = (string)c
                        };

            if (query.Count() == 0) {
                //chat属性のみ
                return null;
            }
            var res = query.First();
            Comment com = new Comment(
                res.Anonymity,
                new DateTime(1970, 1, 1).AddSeconds(res.UnixTimeStamp).ToLocalTime(),
                res.Number,
                (UserLevel?)res.UserLevel,
                res.UserID,
                res.Text
            );
            return com;
        }
        /// <summary>
        /// CommentRecivedイベントを通知する。
        /// </summary>
        /// <param name="e"></param>
        private void OnCommentRecived(CommnetRecivedEventArgs e) {
            if (CommnetRecived != null) {
                CommnetRecived(this, e);
            }
        }

        /// <summary>
        /// コメントの取得を停止します。
        /// </summary>
        public void StopComment() {
            throw new System.NotImplementedException();
        }
    }
}

